﻿using System;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Threading;
using Microsoft.Phone.Info;

namespace WindowsPhoneAudioStreaming.Audio.Streaming
{
    public class BufferedStream : Stream
    {
        private int Frequency = 5;
        private int SleepTime = 30;
        private int LastFrameRead = 0;

        public enum ConnectionStatus {
            Connected,
            NotConnected
        }

        private readonly ManualResetEvent networkState = new ManualResetEvent(false);
        
        public int LowerThreshold { get; private set; }
        private int upperThreshold;
        public int UpperThreshold
        {
            get { return upperThreshold; }
            set { 
                upperThreshold = value;
                LowerThreshold = upperThreshold/10;
            }
        }

        public bool FillingBuffer { get; set; }
        public bool FillToUpperThreshold { get; set; }

        public bool BufferFull(int bufferSize)
        {
            return BytesForWriteAviable < bufferSize;
        }

        private readonly int capacity;
        private readonly byte[] buffer;
        private int writePosition;
        private int readPosition;
        private readonly object readSync = new object();
        private readonly object writeSync = new object();
        private long virtualReadPosition;

        protected int BytesForReadAviable
        {
            get
            {
                if (writePosition >= readPosition)
                {
                    return writePosition - readPosition;
                }
                return capacity - readPosition + writePosition;
            }
        }

        protected int BytesForWriteAviable
        {
            get { return capacity - BytesForReadAviable - 1; }
        }

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override long Length
        {
            get { return Position; }
        }

        public override long Position
        {
            get { return virtualReadPosition; }
            set { readPosition = (int) (value % capacity); }
        }

        public bool BufferingEnabled { get; set; }

        public void ConnectionState(ConnectionStatus state) {
            switch (state) {
                case ConnectionStatus.Connected:
                    networkState.Set();
                    break;
                case ConnectionStatus.NotConnected:
                    networkState.Reset();
                    break;
            }
        }

        public bool WaitForRead(int count)
        {
            if (BufferingEnabled && BytesForReadAviable < LowerThreshold)
            {
                FillToUpperThreshold = true;
            }


            while (BytesForReadAviable < count || FillToUpperThreshold)
            {
                networkState.WaitOne();
                if (BytesForReadAviable > UpperThreshold)
                {
                    FillToUpperThreshold = false;
                }

                OnBufferStateChanged();
                if (BufferingEnabled)
                {

                    Thread.Sleep(1000);
                }
                else
                {
                    Thread.Sleep(10);
                }
            }

            FillingBuffer = BytesForReadAviable < UpperThreshold;
            OnBufferStateChanged();
            
            //if (FillingBuffer)
            //{
            //    if (BytesForReadAviable >= UpperThreshold)
            //    {
            //        FillingBuffer = false;
            //    }
            //    else
            //    {
            //        return false;
            //    }
            //}
            return true;
        }

        public void WaitForWrite(int count)
        {
            while (BytesForWriteAviable < count)
            {
                OnBufferStateChanged();
                Thread.Sleep(3000);
            }
            OnBufferStateChanged();
        }

        public BufferedStream(int capacity = 1000000)
        {
            this.capacity = capacity;
            LowerThreshold = (int) (capacity*0.1);
            UpperThreshold = capacity;
            buffer = new byte[capacity];
        }

        public override void Flush()
        {
            // This is the equivalent of resetting the stream
            readPosition = 0;
            writePosition = 0;
            virtualReadPosition = 0;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new InvalidOperationException("It is impossible to change buffer length");
        }

        private void CheckReadPosition()
        {
            if (readPosition >= capacity)
            {
                readPosition -= capacity;
            }
        }

        private void CheckWritePosition()
        {
            if (writePosition >= capacity)
            {
                writePosition -= capacity;
            }
        }

        public override void Write(byte[] writeBuffer, int offset, int count)
        {
           

            lock (writeSync)
            {
                WaitForWrite(count);
                int num = Math.Min(count, BytesForWriteAviable);
                long normalized;
                for (int i = 0; i < num; i++)
                {
                    normalized = NormalizeIndex(writePosition + i);
                    buffer[(int)(normalized)] = writeBuffer[offset + i];
                }
                writePosition += num;
                CheckWritePosition();
            }
            OnBufferStateChanged();
        }



        public override int Read(byte[] readBuffer, int offset, int count)
        {
            int result;
            lock (readSync)
            {
                var okToRead = WaitForRead(count);
                if (!okToRead && LastFrameRead > Frequency)
                {
                    LastFrameRead = 0;

                    Thread.Sleep(SleepTime);
                }
                LastFrameRead++;
                int num = Math.Min(count, BytesForReadAviable);
                long normalized;
                for (int i = 0; i < num; i++)
                {
                    normalized = NormalizeIndex(readPosition + i);
                    readBuffer[i + offset] = buffer[(int)(normalized)];
                }
                readPosition += num;
                virtualReadPosition += num;
                CheckReadPosition();
                result = num;

                if (BytesForReadAviable < UpperThreshold)
                {
                    FillingBuffer = true;
                }
            }


            return result;
        }

        private long NormalizeIndex(long value)
        {
            if (value < capacity)
            {
                return value;
            }
            return value - capacity;
        }

        private double lastBufferPercentageReported;
        private int isReporting;
        private DateTime lastReport=DateTime.MinValue;
        private void OnBufferStateChanged(double? percentFull = null)
        {
            if (Interlocked.CompareExchange(ref isReporting, 1, 0) == 1) return;

            var newReport = DateTime.Now;
            if (newReport.Subtract(lastReport).TotalSeconds < 0.5)
            {
                Interlocked.Exchange(ref isReporting, 0);
                return;
            }
            lastReport = newReport;

            DebugOutputMemoryUsage();

            // Reporting disabled
            return;

           // ThreadPool.QueueUserWorkItem(asyncobj => InternalOnBufferStateChanged(percentFull));
        }

        [Conditional("DEBUG")]
        private void DebugOutputMemoryUsage(string label = null)
        {
            var limit = DeviceStatus.ApplicationMemoryUsageLimit;
            var current = DeviceStatus.ApplicationCurrentMemoryUsage;
            var remaining = limit - current;
            var peak = DeviceStatus.ApplicationPeakMemoryUsage;
            var safetyMargin = limit - peak;
            if (label != null)
            {
                Debug.WriteLine(label);
            }
            Debug.WriteLine("Memory usage (bytes) Limit:{0} Current:{1} ({2}) Max:{3} ({4})",
                limit,current,remaining,peak,safetyMargin);
            Interlocked.Exchange(ref isReporting, 0);
            //Debug.WriteLine("Memory limit (bytes): " + limit);
            //Debug.WriteLine("Current memory usage: {0} bytes ({1} bytes remaining)", current, remaining);
            //Debug.WriteLine("Peak memory usage: {0} bytes ({1} bytes safety margin)", peak, safetyMargin);
        }

        private void InternalOnBufferStateChanged(double? percentFull = null)
        {
            try
            {

                //if (BufferStateChanged != null)
                //{
                double report;
                if (percentFull.HasValue)
                {
                    report = percentFull.Value;
                }
                else
                {
                    report = (double) BytesForReadAviable/(double) UpperThreshold;

                    // Check for changes +/- 1%
                    if (Math.Abs(report - lastBufferPercentageReported) < 0.01)
                    {
                        return;
                    }
                }

                lastBufferPercentageReported = report;
                ReportBuffering(report);

                // BufferStateChanged(report);
                //}
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                Interlocked.Exchange(ref isReporting, 0);
            }
        }

        private readonly Mutex progressMutex = new Mutex(false, "BufferProgress");

        private void ReportBuffering(double progress)
        {
            TraceLogger.WriteTrace("reportbuffering begin");
            BufferStatus status = BufferStatus.NotBuffering;
            if (progress <= 0.0)
            {
                status = BufferStatus.BufferEmpty;
            }
            else if (progress < 1.0)
            {
                status = BufferStatus.Buffering;
            }

            TraceLogger.WriteTrace("reportbuffering waitone");
            progressMutex.WaitOne();
            TraceLogger.WriteTrace("reportbuffering waitone released");
            try
            {
                //var track = BackgroundAudioPlayer.Instance.Track;
                //if (track != null)
                //{
                //    track.BeginEdit();
                //    track.Tag = status + "|" + progress;
                //    track.EndEdit();
                //}

                using (var file = IsolatedStorageFile.GetUserStoreForApplication().OpenFile("bufferprogress", FileMode.Create, FileAccess.Write))
                using (var writer = new StreamWriter(file))
                {
                    writer.Write(status + "|" + progress);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                progressMutex.ReleaseMutex();
            }
        }
    }
}
